1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.graphics.g2d.textrenderer;
12 import hip.graphics.mesh;
13 import hip.math.matrix;
14 import hip.api.data.font;
15 import hip.hiprenderer;
16 import hip.assetmanager;
17 public import hip.graphics.orthocamera;
18 public import hip.api.graphics.batch;
19 public import hip.api.graphics.text : HipTextAlign;
20 
21 /**
22 *   Don't change those names. If the variable names are changed, the shaders should stop working
23 */
24 @HipShaderInputLayout struct HipTextRendererVertex
25 {
26     import hip.math.vector;
27     Vector3 vPosition;
28     Vector2 vTexST;
29 
30     this(Vector3 vPosition, Vector2 vTexST)
31     {
32         this.vPosition = vPosition;
33         this.vTexST = vTexST;
34     }
35 
36     static enum size_t floatsCount = (HipTextRendererVertex.sizeof / float.sizeof);
37     static enum size_t quadsCount = floatsCount*4;
38 }
39 
40 @HipShaderVertexUniform("Cbuf")
41 struct HipTextRendererVertexUniforms
42 {
43     Matrix4 uModel = Matrix4.identity;
44     Matrix4 uView = Matrix4.identity;
45     Matrix4 uProj = Matrix4.identity;
46 }
47 
48 @HipShaderFragmentUniform("FragVars")
49 struct HipTextRendererFragmentUniforms
50 {
51     float[4] uColor = [1,1,1,1];
52 }
53 
54 
55 enum TextRendererPoolSize = 40_000;
56 private __gshared Shader bmTextShader = null;
57 
58 /**
59 *   This class oculd be refactored in the future to actually
60 * use a spritebatch for its drawing.
61 */
62 class HipTextRenderer : IHipDeferrableText, IHipBatch
63 {
64     mixin(HipDeferredLoad);
65     IHipFont font;
66     Mesh mesh;
67     index_t[] indices;
68     HipTextRendererVertex[] vertices;
69 
70     protected HipColor color;
71     protected HipOrthoCamera camera;
72     protected float managedDepth = 0;
73     private uint quadsCount;
74     private uint lastDrawQuadsCount;
75     bool shouldRenderLineBreak, shouldRenderSpace;
76     private __gshared uint[] linesWidths;
77 
78     this(HipOrthoCamera camera, index_t maxIndices = index_t_maxQuadIndices)
79     {
80         if(bmTextShader is null)
81         {
82             bmTextShader = HipRenderer.newShader(HipShaderPresets.BITMAP_TEXT);
83             bmTextShader.addVarLayout(ShaderVariablesLayout.from!HipTextRendererVertexUniforms);
84             bmTextShader.addVarLayout(ShaderVariablesLayout.from!HipTextRendererFragmentUniforms);
85             bmTextShader.setBlending(HipBlendFunction.SRC_ALPHA, HipBlendFunction.ONE_MINUS_SRC_ALPHA, HipBlendEquation.ADD);
86             const Viewport v = HipRenderer.getCurrentViewport();
87             bmTextShader.uProj = Matrix4.orthoLH(0, v.width, v.height, 0, 0.01, 100);
88             bmTextShader.setDefaultBlock("FragVars");
89             bmTextShader.bind();
90             bmTextShader.sendVars();
91         }
92         mesh = new Mesh(HipVertexArrayObject.getVAO!HipTextRendererVertex, bmTextShader);
93         //6 indices per quad
94         indices = new index_t[](maxIndices);
95         vertices = new HipTextRendererVertex[](TextRendererPoolSize);
96         mesh.createIndexBuffer(maxIndices, HipBufferUsage.STATIC);
97         mesh.createVertexBuffer(cast(index_t)vertices.length, HipBufferUsage.DYNAMIC);
98         mesh.sendAttributes();
99         HipVertexArrayObject.putQuadBatchIndices(indices, maxIndices / 6);
100         mesh.setVertices(vertices);
101         mesh.setIndices(indices);
102         if(camera is null)
103             camera = new HipOrthoCamera();
104         this.camera = camera;
105 
106         import hip.global.gamedef;
107         //Promise it won't modify
108         setFont(cast(IHipFont)HipDefaultAssets.font);
109     }
110 
111     void setCurrentDepth(float depth){managedDepth = depth;}
112 
113     void setFont(IHipFont font)
114     {
115         if(this.font !is null && font !is this.font)
116         {
117             draw();
118         }
119         this.font = font;
120     }
121 
122     void setColor(HipColor color)
123     {
124         if(this.color != color)
125         {
126             if(this.color != HipColor.no)
127                 draw();
128             bmTextShader.uColor = HipColorf(color);
129         }
130         this.color = color;
131     }
132 
133     /** 
134      * Implementation for unchanging text.
135      *  The text will be saved, represented as an internal ID to a managed static HipText. Which means the texture will be baked
136      *  so it is possible to actually draw it a lot faster as all the preprocessings are done once.
137      */
138     void draw(string text, int x, int y, HipTextAlign alignh = HipTextAlign.CENTER, HipTextAlign alignv = HipTextAlign.CENTER, int boundsWidth = -1, int boundsHeight = -1, bool wordWrap = false)
139     {
140         import hip.util.string : toUTF32;
141         import hip.api.graphics.text;
142 
143         dstring str = text.toUTF32;
144         int vI = quadsCount*4; //vertex buffer index
145         bool isFirstLine = true;
146         int yoffset = 0;
147         foreach(HipLineInfo lineInfo; font.wordWrapRange(str, wordWrap ? boundsWidth : -1))
148         {
149             if(!isFirstLine)
150             {
151                 yoffset+= font.lineBreakHeight;
152             }
153             isFirstLine = false;
154             int xoffset = 0;
155             int displayX = void, displayY = void;
156             getPositionFromAlignment(x, y, lineInfo.width, 0, alignh, alignv, displayX, displayY, boundsWidth, boundsHeight);
157             for(int i = 0; i < lineInfo.line.length; i++)
158             {
159                 int kerning = lineInfo.kerningCache[i];
160                 const(HipFontChar)* ch = lineInfo.fontCharCache[i];
161 
162                 switch(lineInfo.line[i])
163                 {
164                     case ' ':
165                         if(!shouldRenderSpace)
166                         {
167                             xoffset+= font.spaceWidth;
168                             break;
169                         }
170                         goto default;
171                     default:
172                         if(ch is null) continue;
173                         ch.putCharacterQuad(
174                             cast(float)(xoffset+displayX+ch.xoffset+kerning),
175                             cast(float)(yoffset+displayY+ch.yoffset), managedDepth,
176                             cast(HipTextRendererVertexAPI[])vertices[vI..vI+4]
177                         );
178                         vI+= 4;
179                         xoffset+= ch.xadvance;
180                 }
181             }
182         }
183         quadsCount = vI/4;
184     }
185 
186     ///This way it will reallocate once.
187     void addVertices(void[] vertices, IHipFont font)
188     {
189         if(vertices.length > 0)
190         {
191             setFont(font);
192             HipTextRendererVertex[] theVerts = cast(HipTextRendererVertex[])vertices;
193             if(theVerts.length+quadsCount*4 > this.vertices.length)
194                 this.vertices.length = theVerts.length+quadsCount*4;
195             this.vertices[quadsCount*4..quadsCount*4+theVerts.length] = theVerts[0..$];
196             quadsCount+= theVerts.length/4;
197         }
198     }
199 
200     void draw()
201     {
202         if(font is null)
203         {
204             import hip.error.handler;
205             ErrorHandler.showWarningMessage("Font Missing", "No font attached on HipTextRenderer");
206             return;
207         }
208         if(quadsCount - lastDrawQuadsCount != 0)
209         {
210             mesh.bind();
211             this.font.texture.bind();
212             mesh.shader.setVertexVar("Cbuf.uProj", camera.proj, true);
213             mesh.shader.setVertexVar("Cbuf.uView", camera.view, true);
214             mesh.shader.sendVars();
215             
216             size_t start = lastDrawQuadsCount*4;
217             size_t end = quadsCount*4;
218 
219             mesh.updateVertices(cast(float[])vertices[start..end], cast(int)start);
220             mesh.draw((quadsCount - lastDrawQuadsCount)*6, HipRendererMode.TRIANGLES, lastDrawQuadsCount*6);
221             font.texture.unbind();
222             mesh.unbind();
223         }
224         lastDrawQuadsCount = quadsCount;
225     }
226     /**
227     *   Flush should be took care since it could make it rewrite to the same part of the buffer agin.
228     *   While shadow buffering is not implemented, use it as that.
229     */
230     void flush()
231     {
232         draw();
233         lastDrawQuadsCount = quadsCount = 0;
234     }
235 }